package cn.rengy.tpa.wxmp.redpacket;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.XmlUtil;
import cn.rengy.lang.DataConverter;
import cn.rengy.lang.ResultEntity;
import cn.rengy.tool.core.util._NumberUtils;
import cn.rengy.tpa.wxmp.lang.JaxbParser;
import cn.rengy.tpa.wxmp.pay.PayCode;
import cn.rengy.tpa.wxmp.pay.exception.PayApiException;
import cn.rengy.tpa.wxmp.pay.exception.PayBusinessException;
import cn.rengy.tpa.wxmp.pay.exception.SignatureException;
import cn.rengy.tpa.wxmp.util.WXPayConstants.SignType;
import cn.rengy.tpa.wxmp.util.WeChatUtil;
import cn.rengy.tpa.wxmp.util.WxRequest;

public class RedPacketManager {
	private static Logger logger = LoggerFactory.getLogger(RedPacketManager.class);
	/**
     * 企业付款
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_PROMOTION_TRANSFERS = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    /**
     * 普通红包
     */
    private static final String HTTPS_API_MCH_WEIXIN_QQ_COM_REDPACK = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
    
    private static final String HTTPS_API_GETHBINFO_URL="https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo";
    
    /**
     * 构建XML解析器JAXBPaser
     *
     * @param clazz
     * @return
     */
    private static JaxbParser buildJAXBParser(Class clazz) {
        JaxbParser parser = new JaxbParser(clazz);
        Field[] fields = clazz.getDeclaredFields();
        String[] fieldNames = new String[fields.length];
        for (int i = 0; i < fields.length; i++) {
            fieldNames[i] = fields[i].getName();
        }
        parser.setCdataNode(fieldNames);
        return parser;
    }
    /**
     * 支付签名算法
     * <p><a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3">开发文档</p>
     *
     * @param object 待签名对象
     * @return
     */
    private static String signatureObject(String mchKey,Object object) {
        Map<String, Object> map = new HashMap<String, Object>();
        Field[] fields = object.getClass().getDeclaredFields();
        // 字典序
        Arrays.sort(fields, new Comparator<Field>() {
            public int compare(Field o1, Field o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        for (Field field : fields) {
            // sign不参与签名
            if ("sign".equals(field.getName())) {
                continue;
            }
            XmlElement xmlElement = field.getAnnotation(XmlElement.class);
            if (xmlElement == null) {
                try {
                    Method getMethod = object.getClass().getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
                    xmlElement = getMethod.getAnnotation(XmlElement.class);
                } catch (NoSuchMethodException e) {
                    logger.warn("get method not found : " + field.getName());
                    // skip this
                }
            }
            field.setAccessible(true);
            try {
                map.put(xmlElement != null ? xmlElement.name() : field.getName(), field.get(object));
            } catch (IllegalAccessException e) {
                // never throws...
            }
        }
        return signatureMap(mchKey,map);
    }
    /**
     * 支付签名算法
     * <p><a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3">开发文档</p>
     *
     * @param map 待签名对象
     * @return
     */
    private static String signatureMap(String mchKey,Map<String, Object> map) {
        map.put("sign", ""); // sign不参与签名
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() != null && !"".equals(entry.getValue())) {
                list.add(entry.getKey() + "=" + entry.getValue() + "&");
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder signatureTemp = new StringBuilder();
        for (int i = 0; i < size; i++) {
            signatureTemp.append(arrayToSort[i]);
        }
        signatureTemp.append("key=").append(mchKey);
        String signatureSource = signatureTemp.toString();
        logger.debug("sign source : " + signatureSource);
        String signature = DigestUtils.md5Hex(signatureSource).toUpperCase();
        logger.debug("sign result : " + signature);
        return signature;
    }
    /**
     * XML串转化成Map
     *
     * @param xmlString
     * @return
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws SAXException
     */
    private static Map<String, Object> getMapFromXMLString(String xmlString) throws ParserConfigurationException, IOException, SAXException {
//        xmlString = xmlString.replaceAll("\n","");
//        xmlString = xmlString.replaceAll("<!\\[CDATA\\[(.*?)\\]\\]","$1");
        //这里用Dom的方式解析回包的最主要目的是防止API新增回包字段
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        ByteArrayInputStream is = null;
        if (xmlString != null && !"".equals(xmlString.trim())) {
            is = new ByteArrayInputStream(xmlString.getBytes());
        }
        Document document = builder.parse(is);
        //获取到document里面的全部结点
        NodeList allNodes = document.getFirstChild().getChildNodes();
        Node node;
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        int i = 0;
        while (i < allNodes.getLength()) {
            node = allNodes.item(i);
            if (node instanceof Element) {
                map.put(node.getNodeName(), node.getTextContent());
            }
            i++;
        }
        return map;

    }
    /**
     * 检查响应结果是否正确
     *
     * @param postResult
     */
    private static void checkAccess(Map<String, Object> map) throws PayApiException {
        if (PayCode.FAIL.equals(map.get("return_code").toString())) {
        	throw new PayApiException(PayCode.FAIL, map.get("return_msg").toString());
        }
    }

    /**
     * 检查业务结果是否正确
     *
     * @param postResult
     */
    private static void checkBusiness(Map<String, Object> map) throws PayBusinessException {
    	if (PayCode.FAIL.equals(map.get("result_code").toString())) {
    		 throw new PayBusinessException(PayCode.FAIL, map.get("err_code").toString(), map.get("err_code_des").toString());
        }
    }
    /**
     * 查询红包
     * @throws PayApiException 
     * @throws PayBusinessException 
     * @throws IOException 
     * @throws FileNotFoundException 
     * @throws CertificateException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyStoreException 
     * @throws KeyManagementException 
     * @throws UnrecoverableKeyException 
     */
    public static GethbinfoResponse gethbinfo(byte[] cert,String mchKey,GethbinfoRequest request) throws PayApiException, PayBusinessException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException{
    	JaxbParser requestParser = buildJAXBParser(GethbinfoRequest.class);
        request.setSign(signatureObject(mchKey,request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n" + postData);
        String postResult=WxRequest.postSSL(HTTPS_API_GETHBINFO_URL, postData,cert,request.getMch_id());
        logger.info("post result \n" + postResult);
        Map<String, Object> resultMap=null;
        try {
        	resultMap = getMapFromXMLString(postResult);
        } catch (Exception e) {
            logger.error("回包数据解析失败", e);
            throw new PayApiException(PayCode.FAIL, "回包数据解析失败");
        }
        checkAccess(resultMap);
        checkBusiness(resultMap);
        GethbinfoResponse response;
		try {
			response = (GethbinfoResponse) JaxbParser.xmlToBean(GethbinfoResponse.class,postResult);
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new PayApiException(PayCode.FAIL, "回包xml数据解析失败");
		}
        return response;
    }
    /**
     * 发放普通红包
     * @param certPath
     * @param request
     * @return
     * @throws SignatureException
     * @throws PayApiException
     * @throws PayBusinessException
     * @throws IOException 
     * @throws FileNotFoundException 
     * @throws CertificateException 
     * @throws NoSuchAlgorithmException 
     * @throws KeyStoreException 
     * @throws KeyManagementException 
     * @throws UnrecoverableKeyException 
     */
    public static RedpackResponse sendredpack(byte[] cert,String mchKey,RedpackRequest request) throws SignatureException, PayApiException, PayBusinessException, UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException {
        JaxbParser requestParser = buildJAXBParser(RedpackRequest.class);
        
        request.setSign(signatureObject(mchKey,request));
        String postData = requestParser.toXML(request);
        logger.info("post data \n" + postData);
        String postResult=WxRequest.postSSL(HTTPS_API_MCH_WEIXIN_QQ_COM_REDPACK, postData,cert,request.getMch_id());
        logger.info("result \n" + postResult);
        //System.out.println("result \n" + postResult);
        Map<String, Object> resultMap=null;
        try {
        	resultMap = getMapFromXMLString(postResult);
        } catch (Exception e) {
            logger.error("回包数据解析失败", e);
            throw new PayApiException(PayCode.FAIL, "回包数据解析失败");
        }
        checkAccess(resultMap);
        checkBusiness(resultMap);
        JaxbParser responseParser = buildJAXBParser(RedpackResponse.class);
        RedpackResponse response = (RedpackResponse) responseParser.toObj(postResult);
        return response;
    }
    /**
     * 企业付款
     * @param dataConverter
     * @param mchKey
     * @param transfersDTO
     * @param cert
     * @return
     */
    public static ResultEntity<String> transfers(DataConverter dataConverter,String mchKey,TransfersDTO transfersDTO,byte[] cert) {
    	
    	TransfersRO transfersRO=new TransfersRO();
    	transfersRO.setAmount(_NumberUtils.yuanToFen(transfersDTO.getAmount()));
    	transfersRO.setDesc(transfersDTO.getDesc());
    	transfersRO.setDevice_info(transfersDTO.getDevice_info());
    	transfersRO.setMch_appid(transfersDTO.getMch_appid());
    	transfersRO.setMchid(transfersDTO.getMchid());
    	String nonce_str=RandomStringUtils.randomAlphanumeric(32);
    	transfersRO.setNonce_str(nonce_str);
    	transfersRO.setOpenid(transfersDTO.getOpenid());
    	transfersRO.setPartner_trade_no(transfersDTO.getPartner_trade_no());
    	transfersRO.setSpbill_create_ip(transfersDTO.getSpbill_create_ip());
    	String sign=WeChatUtil.signatureBean(SignType.MD5, mchKey, transfersRO);
    	transfersRO.setSign(sign);
    	String post=null;
		try {
			post = dataConverter.beanToXml(transfersRO);
		} catch (Exception e) {
			logger.error(ExceptionUtil.stacktraceToString(e,10000));
			return ResultEntity.fail("ERROR","beanToXml解析时出错");
		}
    	
		logger.info("企业付款request:{}",post);
		String result;
		try {
			result=WxRequest.postSSL(HTTPS_API_MCH_WEIXIN_QQ_COM_PROMOTION_TRANSFERS, post,cert,transfersRO.getMchid());
		} catch ( Exception e) {
			logger.error(ExceptionUtil.stacktraceToString(e,10000));
			return ResultEntity.fail("ERROR","企业付款请求微信服务器时出错");
		}
		
		logger.info("企业付款response:{}",result);
    	
		Document document = XmlUtil.parseXml(result);
    	Map<String,Object> resultMap=XmlUtil.xmlToMap(document.getDocumentElement());
        
        String return_code= MapUtils.getString(resultMap, "return_code");
        if (PayCode.FAIL.toString().equals(return_code)){
        	String return_msg=MapUtils.getString(resultMap,"return_msg");
        	logger.error("企业付款失败,return_msg:{}", return_msg);
        	//throw new PayApiException(PayCode.FAIL, .toString());
        	return ResultEntity.fail(return_msg);
        }
        String result_code=MapUtils.getString(resultMap,"result_code");
        if (PayCode.FAIL.toString().equals(result_code)) {
    		String err_code=MapUtils.getString(resultMap,"err_code");
        	String err_code_des=MapUtils.getString(resultMap,"err_code_des");
        	logger.error("企业付款失败，code:{},msg:{}",err_code,err_code_des);
        	return ResultEntity.fail(err_code,err_code_des);
        }
        
        String payment_no =MapUtils.getString(resultMap, "payment_no");//微信付款单号
        
        return ResultEntity.ok(payment_no);
    }
}
